﻿using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using UMC.Data;
using UMC.Net;

namespace UMC.Host
{

    class HttpSocket : IDisposable
    {
        // public string Scheme => "http";
        int _timeOut = 20;
        public int TimeOut => _timeOut;
        UMC.Net.INetHandler webServlet;
        ByteChunk mimeMemory;
        static int PID = 0;
        public HttpSocket(Socket socket, String host, String ip, UMC.Net.INetHandler webServlet)
        {
            _Socket = socket;
            mimeMemory = new ByteChunk(0x1000000);
            this.ActiveTime = UMC.Data.Utility.TimeSpan();

            PID = System.Threading.Interlocked.Increment(ref PID) % 100000;
            this.pid = PID + 1;

            this._Host = host;

            this._remoteIpAddress = ip;

            HttpServier.httpMimes.TryAdd(pid, this);
            this.webServlet = webServlet;

            SocketAsyncEventArgs eventArgs = new SocketAsyncEventArgs();

            eventArgs.SetBuffer(_data);
            eventArgs.Completed += Http;

            if (!this._Socket.ReceiveAsync(eventArgs))
            {
                ProcessReceive(eventArgs);
            }

        }
        Socket _Socket;
        int pid = 0;
        public int Id => pid;
        String _remoteIpAddress, _Host;
        public String Host => _Host;
        public String RemoteIpAddress => _remoteIpAddress;
        public bool Connected => _Socket.Connected;

        public void Write(ReadOnlySpan<byte> bytes)
        {
            if (isDispose == false)
            {
                try
                {

                    _Socket.Send(bytes, SocketFlags.None);

                }
                catch
                {
                    this.Dispose();
                }
            }
        }
        public void Write(byte[] buffer, int offset, int count)
        {
            if (isDispose == false)
            {
                try
                {
                    _Socket.Send(buffer, offset, count, SocketFlags.None);

                }
                catch
                {
                    this.Dispose();
                }
            }
        }

        public void Error(int status, String text)
        {
            Error(this, status, text);
        }
        public static void Error(HttpSocket mime, int status, String text)
        {
            var str = System.Text.Encoding.UTF8.GetBytes(text);
            var sb = new StringBuilder();
            sb.Append($"HTTP/1.1 {status} {HttpStatusDescription.Get(status)}\r\n");
            sb.Append("Content-Type: text/plain; charset=utf-8\r\n");
            sb.Append($"Content-Length: {str.Length}\r\n");
            sb.Append("Connection: close\r\n");
            sb.Append("Server: APIUMC\r\n\r\n");

            var b = System.Text.Encoding.UTF8.GetBytes(sb.ToString());

            mime.Write(b, 0, b.Length);
            mime.Write(str, 0, str.Length);

            mime.Dispose();


        }
        private void Http(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    ProcessReceive(e);
                    break;
                default:
                    break;
            }
        }
        UMC.Net.Mime Request;
        void ProcessReceive(SocketAsyncEventArgs e)
        {
            if (processReceive(e))
            {
                while (isDispose == false && !_Socket.ReceiveAsync(e))
                {
                    if (!processReceive(e))
                    {
                        break;
                    }
                }
            }
        }
        private bool processReceive(SocketAsyncEventArgs e)
        {
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {
                this.ActiveTime = UMC.Data.Utility.TimeSpan();
                try
                {
                    if (Request == null)
                    {
                        mimeMemory.Reset();
                        Request = new HttpRequest(this, mimeMemory);
                    }
                    var req = this.Request;
                    var size = e.Offset + e.BytesTransferred;
                    var data = this._data;
                    int len = req.Receive(this._data, e.Offset, e.BytesTransferred);

                    if (req.IsHttpFormatError)
                    {
                        this.Dispose();
                        return false;
                    }
                    if (len > 0)
                    {
                        Array.Copy(data, size - len, data, 0, len);
                        e.SetBuffer(data, len, data.Length - len);
                    }
                    else
                    {
                        e.SetBuffer(data);
                    }
                    if (this.isDispose == false)
                    {
                        return true;
                    }

                    return true;
                }
                catch (Exception ex)
                {
                    Utility.Error("Http", ex.ToString());
                    Error(this, 500, $"500 InternalServerError\r\n\r\n{ex}");

                }
            }
            else
            {
                this.Dispose();

            }
            return false;
        }




        public void OutputFinish()
        {

            this.ActiveTime = UMC.Data.Utility.TimeSpan();
            _timeOut = 20;
            Request = null;
        }
        public uint ActiveTime
        {

            get; set;
        }
        bool isDispose = false;
        public void Dispose()
        {

            if (isDispose == false)
            {
                isDispose = true;
                System.Buffers.ArrayPool<byte>.Shared.Return(this._data);
                try
                {
                    _Socket.Shutdown(SocketShutdown.Both);
                }
                catch
                {

                }
                _Socket.Close();
            }
            HttpSocket link;
            HttpServier.httpMimes.TryRemove(pid, out link);

        }

        byte[] _data = System.Buffers.ArrayPool<byte>.Shared.Rent(0x600);

        public void PrepareRespone(HttpRequest request)
        {

            _timeOut = 300;
            if (request.IsWebSocket)
            {

                if (request.PathQuery.Span.StartsWith("/UMC.WS/"u8))
                {
                    var Device = request.PathQuery.Length > 8 ? request.PathQuery.Slice(8) : request.Cookies.Get("device"u8);
                    if (Device.IsEmpty == false)
                    {
                        var secWebSocketKey = request.Headers.Get("Sec-WebSocket-Key"u8);
                        if (secWebSocketKey.IsEmpty == false)
                        {
                            var buffers = System.Buffers.ArrayPool<byte>.Shared.Rent(0x200);
                            try
                            {

                                this.Request = new UMC.Host.HttpWebSocket(this.Write, UMC.Data.Utility.Guid(Device.Span.UTF8(), true).Value, this.Dispose, request.Memory);
                                secWebSocketKey.Span.CopyTo(buffers);
                                var size = secWebSocketKey.Span.Length;
                                size += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".WriteBytes(buffers, size);
                                var SHA1 = System.Security.Cryptography.SHA1.Create();
                                var lens = SHA1.ComputeHash(buffers, 0, size);//, buffers.AsSpan(size, 24));
                                string secWebSocketAcceptString = Convert.ToBase64String(lens);//buffers.AsSpan(size, len));
                                var writer = new Net.TextWriter(request._context.Write, buffers);
                                writer.Write($"HTTP/1.1 101 {HttpStatusDescription.Get(101)}\r\n");
                                writer.Write("Connection: Upgrade\r\n");
                                writer.Write("Upgrade: websocket\r\n");
                                writer.Write($"Sec-WebSocket-Accept: {secWebSocketAcceptString}\r\n");
                                writer.Write("Server: Apiumc\r\n\r\n");
                                writer.Flush();
                                writer.Dispose();
                            }
                            finally
                            {
                                System.Buffers.ArrayPool<byte>.Shared.Return(buffers);

                            }

                        }
                        else
                        {
                            Error(this, 403, "not validate websocket headers");
                        }
                    }
                    else
                    {
                        Error(this, 403, "not device cookie");
                    }
                }

            }
            else
            {

                var context = new HttpContext(request, new HttpResponse(this, request));
                try
                {
                    context.ProcessRequest(this.webServlet);
                }
                catch (Exception ex)
                {
                    context.Error(ex);
                }
            }

        }
    }

}

